import pygame, sys, random
from pygame.locals import *
BOARDWIDTH = 4
BOARDHEIGHT = 4
RANDOMSLIDENUMBER = BOARDWIDTH * BOARDHEIGHT * 10

TILESIZE = 80
GAPSIZE = 2
BORDERSIZE = 20
ANIMATIONSPEED = 5
WINDOWWIDTH = 640
WINDOWHEIGHT = 480
XMARGIN = (WINDOWWIDTH - ((TILESIZE + GAPSIZE) * BOARDWIDTH)) // 2
YMARGIN = (WINDOWHEIGHT - ((TILESIZE + GAPSIZE) * BOARDHEIGHT)) // 2
FPS = 30
DARKTURQUOISE =(3,54,73)
GREEN = (0,204,0)
WHITE = (255,255,255)
BRIGHTBLUE = (0,50,255)
BGCOLOR = DARKTURQUOISE
TILECOLOR = GREEN
TEXTCOLOR = WHITE
BORDERCOLOR = BRIGHTBLUE
BASICFONTSIZE = 30

UP = '向上'
DOWN = '向下'
LEFT = '向左'
RIGHT = '向右'

def resetAnimation(board, allMoves):
    revAllMoves = allMoves[:]
    revAllMoves.reverse()
    for move in revAllMoves:
        if move == UP:
            oppositeMove = DOWN
        elif move == DOWN:
            oppositeMove = UP
        elif move == RIGHT:
            oppositeMove = LEFT
        elif move == LEFT:
            oppositeMove = RIGHT
        slideAnimation(board, oppositeMove)
        makeMove(board, oppositeMove)
        drawBoard(board)
def getSpotClicked(x, y):
    for tileX in range(BOARDWIDTH):
        for tileY in range(BOARDHEIGHT):
            left, top = getLeftTopOfTile(tileX, tileY)
            tileRect = pygame.Rect(left, top, TILESIZE, TILESIZE)
            if tileRect.collidepoint(x, y):
                return (tileX, tileY)
    return (None, None)
def makeMove(board, direction):
    blankx, blanky = getBlankPosition(board)
    if direction == UP:
        board[blankx][blanky], board[blankx][blanky+1] = board[blankx][blanky+1], board[blankx][blanky]
    elif direction == DOWN:
        board[blankx][blanky], board[blankx][blanky-1] = board[blankx][blanky-1], board[blankx][blanky]
    elif direction == LEFT:
        board[blankx][blanky], board[blankx+1][blanky] = board[blankx+1][blanky], board[blankx][blanky]
    elif direction == RIGHT:
        board[blankx][blanky], board[blankx-1][blanky] = board[blankx-1][blanky], board[blankx][blanky]
def isSolved(board):
    return board == getStartingBoard()
def isValidMove(board, direction):
    blankx, blanky = getBlankPosition(board)
    if direction == UP:
        return blanky != BOARDHEIGHT - 1
    elif direction == DOWN:
        return blanky != 0
    elif direction == LEFT:
        return blankx != BOARDWIDTH - 1
    elif direction == RIGHT:
        return blankx != 0
    
def getBlankPosition(board):
    for x in range(BOARDWIDTH):
        for y in range(BOARDHEIGHT):
            if board[x][y] == None:
                return (x, y)
def slideAnimation(board, direction):
    blankx, blanky = getBlankPosition(board)
    if direction == UP:
        movex, movey = blankx, blanky + 1
    elif direction == DOWN:
        movex, movey = blankx, blanky - 1
    elif direction == LEFT:
        movex, movey = blankx + 1, blanky
    elif direction == RIGHT:
        movex, movey = blankx - 1, blanky   
    #用背景色覆盖要移动的方块
    baseSurf = DISPLAYSURFACE.copy()
    moveLeft, moveTop = getLeftTopOfTile(movex, movey)
    pygame.draw.rect(baseSurf, BGCOLOR, (moveLeft, moveTop, TILESIZE, TILESIZE))
    #不断重画这个移动的方块，实现滑动的动画
    for i in range(0, TILESIZE+GAPSIZE, ANIMATIONSPEED):
        DISPLAYSURFACE.blit(baseSurf, (0, 0))
        if direction == UP:
            drawTile(movex, movey, board[movex][movey], 0, -i)
        elif direction == DOWN:
            drawTile(movex, movey, board[movex][movey], 0, i)
        elif direction == LEFT:
            drawTile(movex, movey, board[movex][movey], -i, 0)
        elif direction == RIGHT:
            drawTile(movex, movey, board[movex][movey], i, 0)
        pygame.display.update()
        FPSCLOCK.tick(FPS)
def getStartingBoard():
    counter = 1
    board = []
    num = [i + 1 for i in range(BOARDWIDTH * BOARDHEIGHT)]
    for x in range(BOARDWIDTH):
        column = []
        for y in range(BOARDHEIGHT):
            column.append(num[x + y * BOARDWIDTH])
        board.append(column)
    board[BOARDWIDTH-1][BOARDHEIGHT-1] = None
    return board
def getRandomMove(board, lastMove=None):
    validMoves = [UP, DOWN, LEFT, RIGHT]
    if lastMove == UP or not isValidMove(board, DOWN):
        validMoves.remove(DOWN)
    if lastMove == DOWN or not isValidMove(board, UP):
        validMoves.remove(UP)
    if lastMove == RIGHT or not isValidMove(board, LEFT):
        validMoves.remove(LEFT)
    if lastMove == LEFT or not isValidMove(board, RIGHT):
        validMoves.remove(RIGHT)
    return random.choice(validMoves)
def generateNewPuzzle():
    board = getStartingBoard()
    sequence = []
    lastMove = None
    for i in range(RANDOMSLIDENUMBER):
        move = getRandomMove(board, lastMove)
        makeMove(board, move)
        sequence.append(move)
        lastMove = move
    return board, sequence
def getLeftTopOfTile(tileX, tileY):
    left = XMARGIN + (tileX * (TILESIZE + GAPSIZE)) + GAPSIZE // 2
    top = YMARGIN + (tileY * (TILESIZE + GAPSIZE)) + GAPSIZE // 2
    return (left, top)
def drawTile(tilex, tiley, number, adjx=0, adjy=0):
    left, top = getLeftTopOfTile(tilex, tiley)
    pygame.draw.rect(DISPLAYSURFACE, TILECOLOR, (left+adjx, top+adjy, TILESIZE, TILESIZE))
    textSurf = BASICFONT.render(str(number), True, TEXTCOLOR)
    textRect = textSurf.get_rect()
    textRect.center = left + TILESIZE//2 + adjx, top + TILESIZE//2 + adjy
    DISPLAYSURFACE.blit(textSurf, textRect)
def drawBoard(board):
    DISPLAYSURFACE.fill(BGCOLOR)
    for x in range(BOARDWIDTH):
        for y in range(BOARDHEIGHT):
            if board[x][y]:
                drawTile(x, y, board[x][y])
    width = BOARDWIDTH * (TILESIZE + GAPSIZE) + BORDERSIZE * 2
    height = BOARDHEIGHT * (TILESIZE + GAPSIZE) + BORDERSIZE * 2
    pygame.draw.rect(DISPLAYSURFACE, BORDERCOLOR, (XMARGIN-BORDERSIZE, YMARGIN-BORDERSIZE, width, height), BORDERSIZE)
def checkForQuit():
    for event in pygame.event.get(QUIT):
        terminate()
    for event in pygame.event.get(KEYUP):
        if event.key == K_ESCAPE:
            terminate()
        pygame.event.post(event)
def terminate():
    pygame.quit()
    sys.exit()
def slidepuzzle():
    global DISPLAYSURFACE, BASICFONT, FPSCLOCK
    pygame.init()
    FPSCLOCK = pygame.time.Clock()
    DISPLAYSURFACE = pygame.display.set_mode((WINDOWWIDTH, WINDOWHEIGHT), 0, 32)
    pygame.display.set_caption("Slide Puzzle 数字华容道（数字滑块拼图）让数字顺序排列")
    BASICFONT = pygame.font.Font('res/msyh.ttc', BASICFONTSIZE)
#     BASICFONT = pygame.font.SysFont('微软雅黑', BASICFONTSIZE, True)
    mainBoard, solutionSeq = generateNewPuzzle()
    manualMoves = []
    while True:
        slideTo = None
        drawBoard(mainBoard)
        checkForQuit()
        for event in pygame.event.get():
            if event.type == KEYUP:
                if event.key in (K_LEFT, K_a) and isValidMove(mainBoard, LEFT):
                    slideTo = LEFT
                elif event.key in (K_RIGHT, K_d) and isValidMove(mainBoard, RIGHT):
                    slideTo = RIGHT
                elif event.key in (K_UP, K_w) and isValidMove(mainBoard, UP):
                    slideTo = UP
                elif event.key in (K_DOWN, K_s) and isValidMove(mainBoard, DOWN):
                    slideTo = DOWN
            elif event.type == MOUSEBUTTONUP:
                spotx, spoty = getSpotClicked(event.pos[0], event.pos[1])
                if (spotx, spoty) != (None, None):
                    blankx, blanky = getBlankPosition(mainBoard)
                    if spotx == blankx and spoty == blanky+1:
                        slideTo = UP
                    elif spotx == blankx and spoty == blanky-1:
                        slideTo = DOWN
                    elif spotx == blankx+1 and spoty ==blanky:
                        slideTo = LEFT
                    elif spotx == blankx-1 and spoty == blanky:
                        slideTo = RIGHT
                else:
                    if isSolved(mainBoard):
                        mainBoard, solutionSeq = generateNewPuzzle()
                    else:
                        resetAnimation(mainBoard, solutionSeq + manualMoves)
                        manualMoves = []

        if slideTo:
            pygame.mixer.Sound('res/blockslide.mp3').play()
            slideAnimation(mainBoard, slideTo)
            makeMove(mainBoard, slideTo)
            manualMoves.append(slideTo)
            if isSolved(mainBoard):
                pygame.mixer.Sound('res/you_win.mp3').play()
                gameOverRender = BASICFONT.render("恭喜！解密成功！", True, (0,128,0))
                DISPLAYSURFACE.fill(BGCOLOR)
                DISPLAYSURFACE.blit(gameOverRender, [WINDOWWIDTH//2, WINDOWHEIGHT//2 - 36])
                pygame.display.update()
                while True:
                    checkForQuit()
        pygame.display.update()
        FPSCLOCK.tick(FPS)
if __name__ == "__main__":
    slidepuzzle()